{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## PromptTemplate\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from dotenv import load_dotenv\n",
    "\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH02-Prompt\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "LLM 객체를 정의합니다. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "llm = ChatOpenAI()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 방법 1. from_template() 메소드를 사용하여 PromptTemplate 객체 생성\n",
    "\n",
    "- 치환될 변수를 `{ 변수 }` 로 묶어서 템플릿을 정의합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "\n",
    "# template 정의. {country}는 변수로, 이후에 값이 들어갈 자리를 의미\n",
    "template = \"{country}의 수도는 어디인가요?\"\n",
    "\n",
    "# from_template 메소드를 이용하여 PromptTemplate 객체 생성\n",
    "prompt = PromptTemplate.from_template(template)\n",
    "prompt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`country` 변수에 값을 넣어서 문장을 생성할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# prompt 생성. format 메소드를 이용하여 변수에 값을 넣어줌\n",
    "prompt = prompt.format(country=\"대한민국\")\n",
    "prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# template 정의\n",
    "template = \"{country}의 수도는 어디인가요?\"\n",
    "\n",
    "# from_template 메소드를 이용하여 PromptTemplate 객체 생성\n",
    "prompt = PromptTemplate.from_template(template)\n",
    "\n",
    "# chain 생성\n",
    "chain = prompt | llm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# country 변수에 입력된 값이 자동으로 치환되어 수행됨\n",
    "chain.invoke(\"대한민국\").content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 방법 2. PromptTemplate 객체 생성과 동시에 prompt 생성\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "추가 유효성 검사를 위해 `input_variables` 를 명시적으로 지정하세요.\n",
    "\n",
    "이러한 변수는 인스턴스화 중에 템플릿 문자열에 있는 변수와 비교하여 불일치하는 경우 예외를 발생시킵니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# template 정의\n",
    "template = \"{country}의 수도는 어디인가요?\"\n",
    "\n",
    "# PromptTemplate 객체를 활용하여 prompt_template 생성\n",
    "prompt = PromptTemplate(\n",
    "    template=template,\n",
    "    input_variables=[\"country\"],\n",
    ")\n",
    "\n",
    "prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# prompt 생성\n",
    "prompt.format(country=\"대한민국\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# template 정의\n",
    "template = \"{country1}과 {country2}의 수도는 각각 어디인가요?\"\n",
    "\n",
    "# PromptTemplate 객체를 활용하여 prompt_template 생성\n",
    "prompt = PromptTemplate(\n",
    "    template=template,\n",
    "    input_variables=[\"country1\"],\n",
    "    partial_variables={\n",
    "        \"country2\": \"미국\"  # dictionary 형태로 partial_variables를 전달\n",
    "    },\n",
    ")\n",
    "\n",
    "prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt.format(country1=\"대한민국\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt_partial = prompt.partial(country2=\"캐나다\")\n",
    "prompt_partial"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt_partial.format(country1=\"대한민국\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = prompt_partial | llm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain.invoke(\"대한민국\").content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain.invoke({\"country1\": \"대한민국\", \"country2\": \"호주\"}).content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### `partial_variables`: 부분 변수 채움\n",
    "\n",
    "`partial`을 사용하는 일반적인 용도는 함수를 부분적으로 사용하는 것입니다. 이 사용 사례는 **항상 공통된 방식으로 가져오고 싶은 변수** 가 있는 경우입니다.\n",
    "\n",
    "대표적인 예가 **날짜나 시간** 입니다.\n",
    "\n",
    "항상 현재 날짜가 표시되기를 원하는 프롬프트가 있다고 가정해 보겠습니다. 프롬프트에 하드 코딩할 수도 없고, 다른 입력 변수와 함께 전달하는 것도 번거롭습니다. 이 경우 항상 현재 **날짜를 반환하는 함수** 를 사용하여 프롬프트를 부분적으로 변경할 수 있으면 매우 편리합니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "다음의 코드는 오늘 날짜를 구하는 파이썬 코드입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "# 오늘 날짜를 출력\n",
    "datetime.now().strftime(\"%B %d\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 날짜를 반환하는 함수 정의\n",
    "def get_today():\n",
    "    return datetime.now().strftime(\"%B %d\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = PromptTemplate(\n",
    "    template=\"오늘의 날짜는 {today} 입니다. 오늘이 생일인 유명인 {n}명을 나열해 주세요. 생년월일을 표기해주세요.\",\n",
    "    input_variables=[\"n\"],\n",
    "    partial_variables={\n",
    "        \"today\": get_today  # dictionary 형태로 partial_variables를 전달\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# prompt 생성\n",
    "prompt.format(n=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 을 생성합니다.\n",
    "chain = prompt | llm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 을 실행 후 결과를 확인합니다.\n",
    "print(chain.invoke(3).content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 을 실행 후 결과를 확인합니다.\n",
    "print(chain.invoke({\"today\": \"Jan 02\", \"n\": 3}).content)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 파일로부터 template 읽어오기\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import load_prompt\n",
    "\n",
    "prompt = load_prompt(\"prompts/fruit_color.yaml\", encoding=\"utf-8\")\n",
    "prompt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Window 사용자 중 이전의 코드가 오류가 나는 경우 아래의 코드로 실행하세요(인코딩 설정)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_teddynote.prompts import load_prompt\n",
    "\n",
    "# Windows 사용자 only: 인코딩을 cp949로 설정\n",
    "load_prompt(\"prompts/fruit_color.yaml\", encoding=\"utf-8\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt.format(fruit=\"사과\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt2 = load_prompt(\"prompts/capital.yaml\")\n",
    "print(prompt2.format(country=\"대한민국\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ChatPromptTemplate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`ChatPromptTemplate` 은 대화목록을 프롬프트로 주입하고자 할 때 활용할 수 있습니다.\n",
    "\n",
    "메시지는 튜플(tuple) 형식으로 구성하며, (`role`, `message`) 로 구성하여 리스트로 생성할 수 있습니다.\n",
    "\n",
    "**role**\n",
    "- `\"system\"`: 시스템 설정 메시지 입니다. 주로 전역설정과 관련된 프롬프트입니다.\n",
    "- `\"human\"` : 사용자 입력 메시지 입니다.\n",
    "- `\"ai\"`: AI 의 답변 메시지입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "chat_prompt = ChatPromptTemplate.from_template(\"{country}의 수도는 어디인가요?\")\n",
    "chat_prompt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chat_prompt.format(country=\"대한민국\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "\n",
    "chat_template = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        # role, message\n",
    "        (\"system\", \"당신은 친절한 AI 어시스턴트입니다. 당신의 이름은 {name} 입니다.\"),\n",
    "        (\"human\", \"반가워요!\"),\n",
    "        (\"ai\", \"안녕하세요! 무엇을 도와드릴까요?\"),\n",
    "        (\"human\", \"{user_input}\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "# 챗 message 를 생성합니다.\n",
    "messages = chat_template.format_messages(\n",
    "    name=\"테디\", user_input=\"당신의 이름은 무엇입니까?\"\n",
    ")\n",
    "messages"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "생성한 메시지를 바로 주입하여 결과를 받을 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "llm.invoke(messages).content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "이번에는 체인을 생성해 보겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain = chat_template | llm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "chain.invoke({\"name\": \"Teddy\", \"user_input\": \"당신의 이름은 무엇입니까?\"}).content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## MessagePlaceholder\n",
    "\n",
    "또한 LangChain은 포맷하는 동안 렌더링할 메시지를 완전히 제어할 수 있는 `MessagePlaceholder` 를 제공합니다. \n",
    "\n",
    "메시지 프롬프트 템플릿에 어떤 역할을 사용해야 할지 확실하지 않거나 서식 지정 중에 메시지 목록을 삽입하려는 경우 유용할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "chat_prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"당신은 요약 전문 AI 어시스턴트입니다. 당신의 임무는 주요 키워드로 대화를 요약하는 것입니다.\",\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=\"conversation\"),\n",
    "        (\"human\", \"지금까지의 대화를 {word_count} 단어로 요약합니다.\"),\n",
    "    ]\n",
    ")\n",
    "chat_prompt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`conversation` 대화목록을 나중에 추가하고자 할 때 `MessagesPlaceholder` 를 사용할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "formatted_chat_prompt = chat_prompt.format(\n",
    "    word_count=5,\n",
    "    conversation=[\n",
    "        (\"human\", \"안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다.\"),\n",
    "        (\"ai\", \"반가워요! 앞으로 잘 부탁 드립니다.\"),\n",
    "    ],\n",
    ")\n",
    "\n",
    "print(formatted_chat_prompt)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 생성\n",
    "chain = chat_prompt | llm | StrOutputParser()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# chain 실행 및 결과확인\n",
    "chain.invoke(\n",
    "    {\n",
    "        \"word_count\": 5,\n",
    "        \"conversation\": [\n",
    "            (\n",
    "                \"human\",\n",
    "                \"안녕하세요! 저는 오늘 새로 입사한 테디 입니다. 만나서 반갑습니다.\",\n",
    "            ),\n",
    "            (\"ai\", \"반가워요! 앞으로 잘 부탁 드립니다.\"),\n",
    "        ],\n",
    "    }\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
